package top.xzxsrq.fileviewutils.plugin;

import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import top.xzxsrq.web.utils.SpringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

@Component
@Log4j2
public class SpringDynamicLoadJar {

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 动态加载指定路径下指定jar包
     *
     * @param path
     */
    public void loadJar(String path) throws ClassNotFoundException {
        // 获取beanFactory
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        // 不使用这个类加载器getSystemClassLoader，会导致无法加载到外部jar的springboot的注解
//            PluginClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{url}, ClassLoader.getSystemClassLoader());
        Map<String, Class<?>> loadedClasses = JarDynamicLoadClassLoader.loadJar(path, SpringDynamicLoadJar.class.getClassLoader());
        if (loadedClasses == null) {
            log.error("{}加载失败", path);
            return;
        }
        // 获取当前项目的执行器
        Set<Class<?>> initBeanClass = new HashSet<>();
        for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {
            String className = entry.getKey();
            Class<?> clazz = entry.getValue();
            // 2. 将有@spring注解的类交给spring管理
            // 2.1 判断是否注入spring
            String flag = SpringUtils.hasSpringAnnotation(clazz);
            if (!flag.equals("pass")) {
                // 2.2交给spring管理
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
                AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
                // 此处beanName使用全路径名是为了防止beanName重复
                String packageName = className.substring(0, className.lastIndexOf(".") + 1);
                String beanName = className.substring(className.lastIndexOf(".") + 1);
                beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
                // 2.3注册到spring的beanFactory中
                beanFactory.registerBeanDefinition(beanName, beanDefinition);
                // 2.4允许注入和反向注入
                beanFactory.autowireBean(clazz);
                beanFactory.initializeBean(clazz, beanName);
                try {
                    // 2.5手动构建实例，并注入base service 防止卸载之后不再生成
                    Object obj = clazz.newInstance();
                    beanFactory.registerSingleton(beanName, obj);
                    //3.特殊处理
                    if ("Controller".equals(flag)) {
                        registerController(beanName);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 动态注册Controller
     */
    private void registerController(String controllerBeanName) {
        RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        // 注册Controller
        Method method = null;
        try {
            method = handlerMapping.getClass().getSuperclass().getSuperclass().
                    getDeclaredMethod("detectHandlerMethods", Object.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        // 将private改为可使用
        assert method != null;
        method.setAccessible(true);
        try {
            method.invoke(handlerMapping, controllerBeanName);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 动态去掉Controller的Mapping
     */
    private void unregisterController(String controllerBeanName) {
        RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Object controller = null;
        try {
            controller = applicationContext.getBean(controllerBeanName);
        } catch (Exception e) {
            log.error("获取bean错误：{}", controllerBeanName);
            return;
        }
        final Class<?> targetClass = controller.getClass();
        ReflectionUtils.doWithMethods(targetClass, method -> {
            Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
            try {
                Method createMappingMethod = RequestMappingHandlerMapping.class.
                        getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
                createMappingMethod.setAccessible(true);
                RequestMappingInfo requestMappingInfo = (RequestMappingInfo)
                        createMappingMethod.invoke(handlerMapping, specificMethod, targetClass);
                if (requestMappingInfo != null) {
                    handlerMapping.unregisterMapping(requestMappingInfo);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
    }

    /**
     * 动态卸载指定路径下指定jar包
     *
     * @param path
     */
    public boolean unloadJar(String path) {
        return JarDynamicLoadClassLoader.unloadJar(path, (loadedClasses) -> {
            // 获取beanFactory，准备从spring中卸载
            DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
            Set<String> beanNames = new HashSet<>();
            for (Map.Entry<String, Class<?>> entry : loadedClasses.entrySet()) {
                String key = entry.getKey();
                String packageName = key.substring(0, key.lastIndexOf(".") + 1);
                String beanName = key.substring(key.lastIndexOf(".") + 1);
                beanName = packageName + beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
                // 获取bean，如果获取失败，表名这个类没有加到spring容器中，则跳出本次循环
                Object bean = null;
                try {
                    bean = applicationContext.getBean(beanName);
                    // 特殊处理
                    // 判断是否注入spring
                    String flag = SpringUtils.hasSpringAnnotation(bean.getClass());
                    if ("Controller".equals(flag)) {
                        unregisterController(beanName);
                    }
                } catch (Exception e) {
                    log.error("spring中没有这个bean：{}", beanName);
                    continue;
                }
                // 2.0从spring中移除，这里的移除是仅仅移除的bean，并未移除bean定义
                beanNames.add(beanName);
                beanFactory.destroyBean(beanName, bean);
            }
            try {
                // 移除bean定义
                Field mergedBeanDefinitions = beanFactory.getClass()
                        .getSuperclass()
                        .getSuperclass().getDeclaredField("mergedBeanDefinitions");
                mergedBeanDefinitions.setAccessible(true);
                Map<String, RootBeanDefinition> rootBeanDefinitionMap = ((Map<String, RootBeanDefinition>) mergedBeanDefinitions.get(beanFactory));
                for (String beanName : beanNames) {
                    beanFactory.removeBeanDefinition(beanName);
                    // 父类bean定义去除
                    rootBeanDefinitionMap.remove(beanName);
                }
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        });
    }
}
